本篇階段:實作挑戰
使用介面:claude.ai(一句提示詞,在對話裡產生完整 Python 專案)
Day 05 做的三個 HTML,共同點是畫面就是產出。網頁打開來能動,題目就算解完了,沒有第二個東西要交。
今天換一個方向。題目本身是演算法,不是畫面,而且是我自己沒寫過、只在論文跟簡報裡看過名字的那種演算法。
想知道的事情很單純:對一個題目只有概念上的理解,沒有實作經驗、沒有現成程式碼可抄,一句話能換回多少東西?
丟出去的提示詞就這一句,沒有規格書、沒有附檔案、沒有補充說明:
幫我建立一個 Python 演算法,主題是利用 ICP (Iterative Closest Point) 或 NDT (Normal Distributions Transform) 去模擬在太空中,登陸艇降落固體行星表面之前,先使用光學感測掃描星球表面,並使用這兩種演算法去找出最佳降落地點。
回來的是三個檔案、一份 README、一張六面板圖表、一個可以拉滑桿的 HTML 圖版。
檔案內容請參考我的 Github 對應的專案連結
Day 05 那邊有一個對照組 DEMO 2 ,提示詞是「幫我製作一個心理學的注意力遊戲」,完全沒提 Stroop、Flanker、Posner、Treisman,那四個派典是 Claude 自己挑的。
今天這句話正好相反,它把該用什麼寫死了。
所以 Claude 不用猜「這題該用什麼技術」,它只要決定中間缺的那幾塊,是我沒有自己實際做過的部分:
「用 ICP 對齊點雲」和「找出最佳降落地點」之間,還隔著一整條管線。
這條管線是它自己接起來的,我原本預期要提供更多資訊,或是會需要決定的實作方向。
實際生出來的是六個階段:
[0] 合成行星表面真值地形(碎形基底 + 撞擊坑 + 岩塊 + 區域傾斜)
│
┌─────┴─────┐
[1a] 軌道器 [1b] 下降段光學掃描
先驗地圖 6 幀,高度 900 → 250 m
=圖像對齊目標 導航位姿含漂移誤差
└─────┬─────┘
[2] 點雲圖像對齊(ICP / NDT / Hybrid)→ 修正每一幀的位姿
[3] 多幀融合 → 0.5 m 解析度 DEM
[4] 危害分析:以 2.5 m 著陸足跡逐格做加權最小平方平面擬合
[5] 硬性約束 → 加權評分 → 非極大值抑制 → Top-5 候選點
有兩塊是我根本沒想到要有的。
第一塊是 [1a] 軌道器先驗地圖。 我原本的想像是「掃描好幾幀,然後把它們互相對齊」。這樣做只會得到一團內部一致、但不知道自己在哪裡的點雲。真正的地形相對導航(Terrain-Relative Navigation, TRN)是拿下降時掃到的地形,去對齊事先由軌道器測繪好的那份地圖,這樣才有絕對位置。
第二塊是 [0] 的真值地形。 它先生了一份「上帝視角」的地形,所有感測資料都是從這份真值取樣加雜訊來的。用處在最後才看得到:選完點之後,可以回頭拿真值驗證這個點到底安不安全。這是自己隨手寫的模擬最容易漏掉的東西,沒有真值就只能說「程式跑完了」,不能說「它做對了」。
至於為什麼非圖像對齊不可?因為如果直接相信慣性導航回報的位置,把六幀點雲疊起來,地圖空間的 RMSE 是 7.536 m;而 7 公尺的誤差疊在一份要判斷 0.3 m 岩塊的地形圖上,整張圖等於被塗糊掉,後面全部免談。
兩個都是點雲圖像對齊的技術 (十年前就聽說過,並且當時看著學長與教授拿來實作一些很酷的東西):有兩坨具有三維座標的點,找出一組旋轉加平移,讓它們能緊貼在一起。差別在「怎麼定義貼得好不好」。
ICP 的做法很直覺:對來源點雲的每一個點,去目標點雲裡找最近的那個點,想辦法讓這些配對的距離 (高斯距離) 變小,反覆迭代。
這次生出來的版本用的是 point-to-plane,不量「點到點」的距離,改量「點到目標局部平面」的距離。理由:行星表面近似平面,point-to-plane 只懲罰法線方向的偏差、允許點沿著切平面滑動,收斂快一個數量級。
NDT 完全不同路:先把目標點雲切成一格一格的 voxel (所以如果拿來做腦 voxel 的定位會怎麼樣?),每一格用一個三維高斯分佈描述「這附近的表面長什麼樣」,整張目標地圖因此變成一個連續的機率場。圖像對齊於是變成「找一組位姿,讓來源點落在機率高的地方」,可以直接用梯度上升解,不必每次迭代都重找最近鄰。
Hybrid 則是先用 NDT 粗對位,再交給 ICP 精修。
原理我大致能理解,但從來沒有進行實作過,因為流程細節其實非常多:例如兩者都要對 SE(3) 做局部參數化、要推 Jacobian、要處理 Gauss-Newton 的正規方程。這部分 Claude 都做完了,而且刻意讓兩者共用同一組參數化與 Jacobian,原因是這樣兩者才能公平比較 (這句話等於 Claude 它是在自己給自己設實驗控制)。
在沒有現在的 LLM 之前,這些內容可能已經足夠做成碩論甚至博論的內容,而且可以應用到實際太空場域,例如火星的登陸等等。
程式跑完會印一張表,--seed 7,六幀掃描:
| 方法 | 地圖 RMSE | 位姿誤差 @ 感測器 | 平均迭代 | 耗時 / 幀 |
|---|---|---|---|---|
| 未圖像對齊(純導航) | 7.536 m | — | — | — |
| ICP | 0.022 m | 0.106 m / 0.05° | 49 | 528 ms |
| NDT | 1.369 m | 1.427 m / 0.52° | 43 | 5289 ms |
| Hybrid | 0.022 m | 0.109 m / 0.05° | 47 | 465 ms |
ICP 把 7.5 m 壓到 2.2 公分,NDT 停在 1.37 m。而 NDT 這個 1.37 m 不是平均意義上的「差一點」,是第 1 幀直接爛掉:逐幀看,NDT 在第 0、2、3、4、5 幀都在 0.03 m 上下,唯獨第 1 幀是 8.054 m,完全沒收斂,一幀就把平均拉爆。
再看收斂盆地。程式有一個 --sweep 模式,刻意給不同大小的初始誤差,每級跑 8 次,看多少次能收斂到 0.3 m 以內:
| 初始水平誤差 | 2 m | 5 m | 10 m | 20 m | 35 m |
|---|---|---|---|---|---|
| ICP | 100% | 100% | 100% | 62% | 0% |
| NDT | 100% | 100% | 75% | 0% | 0% |
這次的 NDT 全面落後。 這邊的實作結果,跟印象中「NDT 的優勢就是收斂盆地比 ICP 寬、對初值比較不敏感」剛好相反。
Claude 自己把這件事寫進 README 了,而且給了解釋:
然後它自己補了一句限制:「這個結論限於此類低起伏地形,不宜外推到城市或室內點雲。」
這一段是我覺得很誠實、不幻想的部分,不是因為結論多正確,而是因為它跑出了一個跟教科書說法不一致的結果,沒有試圖隱藏,也沒有回頭去調參數把 NDT 救到贏。前四天一直在對付的問題正好相反,模型在無法查證的欄位上瞎填、把不確定包裝成確定。
當然也有可能是它把 NDT 寫壞了,這點我沒有去另外獨立驗證,見文末註二。
README 跟程式註解裡留了三個「實作時踩過的坑」。
坑一:姿態誤差不能給太大。
在 900 m 高度下,2° 的姿態誤差會透過力臂效應把點雲橫向甩開約 30 m,兩種演算法都會直接發散。
它的處理方式不是把演算法改強,是把參數改對:真實太空載具有星象儀,姿態知識精度遠優於 0.3°。所以預設值設成 nav_sigma_att_deg=0.25,並且註明「這不是限制,是把模型設對」。
坑二:NDT 需要共變異數膨脹退火,不然直接發散。
體素高斯在法線方向極薄(Σ_zz ~ 0.01 m²),初始誤差有好幾公尺的時候,指數項會直接下溢成零、梯度消失,NDT 連動都不會動。解法是在共變異數上加一個 σ₀²I 把高斯撐胖,再由粗到細逐級縮小,這等同對機率場做高斯平滑。
換句話說,教科書版的 NDT 在這個場景根本跑不起來,要加一層 annealing 才能用。
坑三:最大凸起的基準不能用視窗均值。
危害分析要算「著陸足跡範圍內最高的凸起有多高」,直覺是拿視窗內的高度減掉視窗均值。但這樣做,斜坡本身會被誤判成 0.5 m 級的凸起,全圖沒有任何一點合格。正確做法是減掉該格自己擬合出來的局部平面,坡度歸坡度、凸起歸凸起,兩個指標才不會互相汙染。
三個坑的共同點是:它們都不是「程式寫錯」,是題目本身的物理性質。
圖像對齊完、融合成 0.5 m 的 DEM 之後,剩下的是選點。這一段反而是最像工程規格的部分:以 2.5 m 的著陸足跡半徑,逐格做加權最小平方平面擬合,抽出五個指標。
| 指標 | 為什麼重要 | 預設門檻 |
|---|---|---|
| 坡度 | 翻覆風險、推力向量餘裕 | ≤ 10° |
| 粗糙度(殘差 RMS) | 著陸腳架接觸品質 | ≤ 0.14 m |
| 最大凸起 | 岩塊會頂穿或架空登陸艇 | ≤ 0.30 m |
| 資料覆蓋率 | 感測資料夠不夠可信 | ≥ 75% |
| 轉向距離 | 燃料代價 | ≤ 30 m |
任一項超標,分數直接歸零;其餘的加權平均成一個安全分數,最後做非極大值抑制(最小間隔 5 m),避免五個候選點全部擠在同一塊平地上。

六個面板依序是:真值地形加掃描足跡與下降軌跡、圖像對齊後融合的 DEM、坡度圖(青色等值線是 10° 門檻)、粗糙度圖、安全分數與 Top-5 候選點、逐幀圖像對齊精度(對數座標)。
seed 7 的結果是:可著陸區域只有 319 m²,佔掃描範圍的 6.1%。第一名落在 (11.50, −5.50),估測坡度 1.83°、粗糙度 0.054 m。
再來是我覺得整份程式最關鍵的一行輸出,回頭拿真值地形驗證:
>>> 選定降落點: (11.50, -5.50) m
真值地形驗證 -> 坡度 1.70 deg, 粗糙度 0.049 m (估測值 1.83 deg / 0.054 m)
坡度差 0.13°、粗糙度差 5 mm。這一行的意義是:它不只跑完了,它證明了自己跑對了。
Day 05 的 HTML 是產出本身;今天的 HTML 是產出的後半段。
landing_site_plate.html 一樣是單一檔案、離線可開,但做法差很多:重的計算(圖像對齊、融合、平面擬合)已經在 Python 那邊算完、固化成資料,危害圖以 uint8 量化後 base64 內嵌進 HTML,門檻重算、評分、非極大值抑制則跑在瀏覽器端。
所以拉滑桿是即時的,不需要重跑那 15 秒的模擬。能動的東西有:
其中「限制因子」這個檢視我覺得設計得不錯:不可著陸區以對角影線標示,線的顏色就是最嚴重的那個超標項目,紅是坡度、紫是粗糙度、銅是凸起、灰是覆蓋率。一眼就能看出放寬哪個門檻才換得到面積。
這件事本來要靠反覆改參數重跑才做得到,現在變成拉一下滑桿。把「慢的部分算完固化、快的部分留給瀏覽器」,是這個做法值得筆記的一條。
Day 05 收尾時整理出一條規律:畫面越像專業工具,免責就要寫得越用力。因為那三個 DEMO 的數據都是編的,介面卻在替它們做背書。
今天這個東西的體質不一樣。演算法是可查證的,ICP、NDT 都有原始論文,point-to-plane 的推導、NDT 的梯度與 Hessian 都能對照 Magnusson 2009;結果是可驗證的,有真值地形可以回頭對;失敗也是可重現的,換個 seed 再跑一次就行。
但可驗證不等於可信。README 最後那五條限制,我覺得比程式碼本身更重要,其中兩條特別致命:
也就是說,這份程式跑出 0.022 m,不代表真的登陸艇能做到 0.022 m,它示範的是演算法的行為,不是任務的性能。
這條界線跟 Day 05 講的「教學用的模擬,要對的是形狀,不是數值」是同一件事,只是今天這個東西長得更像真的,所以講清楚更重要。
點雲圖像對齊
地形相對導航與危害偵測避讓
註一:本文所有數字取自
descent_hda.py --seed 7的執行(Python 3.13.9 / numpy 2.3.5 / scipy 1.16.3)。耗時欄位與機器有關,換一台會不一樣;RMSE 與成功率欄位在同一 seed 下可重現。收斂盆地表為每級 8 次試驗,樣本數很小,請當作定性觀察,不是統計證據。
註二:「NDT 在此場景收斂盆地不如 ICP」是這份實作跑出來的結果,不是通用結論。我沒有能力獨立驗證這版 NDT 的實作品質,也沒有拿 PCL 或 Open3D 的官方實作做對照,因此「是 NDT 本質如此」與「是這版 NDT 寫得不夠好」兩種可能都還開著,屬待確認。
註三:本程式是研究用途的模擬,不是飛控軟體。軌道器先驗地圖由同一份真值地形取樣而來、無系統性偏差,這是它能達到公分級精度的主要原因;光學感測被抽象成「已還原的 3D 點雲」,未模擬立體匹配失敗、低太陽角長陰影、塵埃反光等真實退化。任何真實任務的選點判斷都不應以本程式的輸出為依據。
註四:本文對 claude.ai 的功能描述以 2026 年 8 月 20 日的產品行為為準。
註五:本文提供的做法供參考,若有其他的看法或建議歡迎留言。